<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport"
          content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no,viewport-fit=cover">
    <meta name="format-detection" content="telephone=no">
    <style type="text/css">

#watermark {

  position: relative;
  overflow: hidden;
}

#watermark .x {
  position: absolute;
  top: 800;
  left: 400;
  color: #3300ff;
  font-size: 50px;
  pointer-events: none;
  opacity:0.3;
  filter:Alpha(opacity=50);
  -webkit-transform: rotate(45deg);
  -moz-transform: rotate(45deg);
}
    </style>


    <style type="text/css">
 html{color:#333;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;text-rendering:optimizelegibility;font-family:Helvetica Neue,PingFang SC,Verdana,Microsoft Yahei,Hiragino Sans GB,Microsoft Sans Serif,WenQuanYi Micro Hei,sans-serif}html.borderbox *,html.borderbox :after,html.borderbox :before{box-sizing:border-box}article,aside,blockquote,body,button,code,dd,details,dl,dt,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,header,hr,input,legend,li,menu,nav,ol,p,pre,section,td,textarea,th,ul{margin:0;padding:0}article,aside,details,figcaption,figure,footer,header,menu,nav,section{display:block}audio,canvas,video{display:inline-block}body,button,input,select,textarea{font:300 1em/1.8 PingFang SC,Lantinghei SC,Microsoft Yahei,Hiragino Sans GB,Microsoft Sans Serif,WenQuanYi Micro Hei,Helvetica,sans-serif}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}table{border-collapse:collapse;border-spacing:0}fieldset,img{border:0}blockquote{position:relative;color:#999;font-weight:400;border-left:1px solid #1abc9c;padding-left:1em;margin:1em 3em 1em 2em}@media only screen and (max-width:640px){blockquote{margin:1em 0}}abbr,acronym{border-bottom:1px dotted;font-variant:normal}abbr{cursor:help}del{text-decoration:line-through}address,caption,cite,code,dfn,em,th,var{font-style:normal;font-weight:400}ol,ul{list-style:none}caption,th{text-align:left}q:after,q:before{content:""}sub,sup{font-size:75%;line-height:0;position:relative}:root sub,:root sup{vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}a{color:#1abc9c}a:hover{text-decoration:underline}.typo a{border-bottom:1px solid #1abc9c}.typo a:hover{border-bottom-color:#555;color:#555}.typo a:hover,a,ins{text-decoration:none}.typo-u,u{text-decoration:underline}mark{background:#fffdd1;border-bottom:1px solid #ffedce;padding:2px;margin:0 5px}code,pre,pre tt{font-family:Courier,Courier New,monospace}pre{background:hsla(0,0%,97%,.7);border:1px solid #ddd;padding:1em 1.5em;display:block;-webkit-overflow-scrolling:touch}hr{border:none;border-bottom:1px solid #cfcfcf;margin-bottom:.8em;height:10px}.typo-small,figcaption,small{font-size:.9em;color:#888}b,strong{font-weight:700;color:#000}[draggable]{cursor:move}.clearfix:after,.clearfix:before{content:"";display:table}.clearfix:after{clear:both}.clearfix{zoom:1}.textwrap,.textwrap td,.textwrap th{word-wrap:break-word;word-break:break-all}.textwrap-table{table-layout:fixed}.serif{font-family:Palatino,Optima,Georgia,serif}.typo-dl,.typo-form,.typo-hr,.typo-ol,.typo-p,.typo-pre,.typo-table,.typo-ul,.typo dl,.typo form,.typo hr,.typo ol,.typo p,.typo pre,.typo table,.typo ul,blockquote{margin-bottom:1rem}h1,h2,h3,h4,h5,h6{font-family:PingFang SC,Helvetica Neue,Verdana,Microsoft Yahei,Hiragino Sans GB,Microsoft Sans Serif,WenQuanYi Micro Hei,sans-serif;color:#000;line-height:1.35}.typo-h1,.typo-h2,.typo-h3,.typo-h4,.typo-h5,.typo-h6,.typo h1,.typo h2,.typo h3,.typo h4,.typo h5,.typo h6{margin-top:1.2em;margin-bottom:.6em;line-height:1.35}.typo-h1,.typo h1{font-size:2em}.typo-h2,.typo h2{font-size:1.8em}.typo-h3,.typo h3{font-size:1.6em}.typo-h4,.typo h4{font-size:1.4em}.typo-h5,.typo-h6,.typo h5,.typo h6{font-size:1.2em}.typo-ul,.typo ul{margin-left:1.3em;list-style:disc}.typo-ol,.typo ol{list-style:decimal;margin-left:1.9em}.typo-ol ol,.typo-ol ul,.typo-ul ol,.typo-ul ul,.typo li ol,.typo li ul{margin-bottom:.8em;margin-left:2em}.typo-ol ul,.typo-ul ul,.typo li ul{list-style:circle}.typo-table td,.typo-table th,.typo table caption,.typo table td,.typo table th{border:1px solid #ddd;padding:.5em 1em;color:#666}.typo-table th,.typo table th{background:#fbfbfb}.typo-table thead th,.typo table thead th{background:hsla(0,0%,95%,.7)}.typo table caption{border-bottom:none}.typo-input,.typo-textarea{-webkit-appearance:none;border-radius:0}.typo-em,.typo em,caption,legend{color:#000;font-weight:inherit}.typo-em{position:relative}.typo-em:after{position:absolute;top:.65em;left:0;width:100%;overflow:hidden;white-space:nowrap;content:"\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB"}.typo img{max-width:100%}.common-content{font-weight:400;color:#353535;line-height:1.75rem;white-space:normal;word-break:normal;font-size:1rem}.common-content img{display:block;max-width:100%;background-color:#eee}.common-content audio,.common-content video{width:100%;background-color:#eee}.common-content center,.common-content font{margin-top:1rem;display:inline-block}.common-content center{width:100%}.common-content pre{margin-top:1rem;padding-left:0;padding-right:0;position:relative;overflow:hidden}.common-content pre code{font-size:.8rem;font-family:Consolas,Liberation Mono,Menlo,monospace,Courier;display:block;width:100%;box-sizing:border-box;padding-left:1rem;padding-right:1rem;overflow-x:auto}.common-content hr{border:none;margin-top:1.5rem;margin-bottom:1.5rem;border-top:1px solid #f5f5f5;height:1px;background:none}.common-content b,.common-content h1,.common-content h2,.common-content h3,.common-content h4,.common-content h5,.common-content strong{font-weight:700}.common-content h1,.common-content h2{font-size:1.125rem;margin-bottom:.45rem}.common-content h3,.common-content h4,.common-content h5{font-size:1rem;margin-bottom:.45rem}.common-content p{font-weight:400;color:#353535;margin-top:.15rem}.common-content .orange{color:#ff5a05}.common-content .reference{font-size:1rem;color:#888}.custom-rich-content h1{margin-top:0;font-weight:400;font-size:15.25px;border-bottom:1px solid #eee;line-height:2.8}.custom-rich-content li,.custom-rich-content p{font-size:14px;color:#888;line-height:1.6}table.hljs-ln{margin-bottom:0;border-spacing:0;border-collapse:collapse}table.hljs-ln,table.hljs-ln tbody,table.hljs-ln td,table.hljs-ln tr{box-sizing:border-box}table.hljs-ln td{padding:0;border:0}table.hljs-ln td.hljs-ln-numbers{min-width:15px;color:rgba(27,31,35,.3);text-align:right;white-space:nowrap;cursor:pointer;user-select:none}table.hljs-ln td.hljs-ln-code,table.hljs-ln td.hljs-ln-numbers{font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace;font-size:12px;line-height:20px;vertical-align:top}table.hljs-ln td.hljs-ln-code{position:relative;padding-right:10px;padding-left:10px;overflow:visible;color:#24292e;word-wrap:normal;white-space:pre}video::-webkit-media-controls{overflow:hidden!important}video::-webkit-media-controls-enclosure{width:calc(100% + 32px);margin-left:auto}.button-cancel{color:#888;border:1px solid #888;border-radius:3px;margin-right:12px}.button-cancel,.button-primary{-ms-flex-positive:1;flex-grow:1;height:35px;display:inline-block;font-size:15px;text-align:center;line-height:36px}.button-primary{color:#fff;background-color:#ff5a05;border-radius:3px}@font-face{font-family:iconfont;src:url(//at.alicdn.com/t/font_372689_bwwwtosxtzp.eot);src:url(//at.alicdn.com/t/font_372689_bwwwtosxtzp.eot#iefix) format("embedded-opentype"),url(//at.alicdn.com/t/font_372689_bwwwtosxtzp.woff) format("woff"),url(//at.alicdn.com/t/font_372689_bwwwtosxtzp.ttf) format("truetype"),url(//at.alicdn.com/t/font_372689_bwwwtosxtzp.svg#iconfont) format("svg")}@font-face{font-family:player-font;src:url(//at.alicdn.com/t/font_509397_1cyjv4o90qiod2t9.eot);src:url(//at.alicdn.com/t/font_509397_1cyjv4o90qiod2t9.eot#iefix) format("embedded-opentype"),url(//at.alicdn.com/t/font_509397_1cyjv4o90qiod2t9.woff) format("woff"),url(//at.alicdn.com/t/font_509397_1cyjv4o90qiod2t9.ttf) format("truetype"),url(//at.alicdn.com/t/font_509397_1cyjv4o90qiod2t9.svg#player-font) format("svg")}.iconfont{font-family:iconfont!important;font-size:16px;font-style:normal;-webkit-font-smoothing:antialiased;-webkit-text-stroke-width:.2px;-moz-osx-font-smoothing:grayscale}html{background:#fff;min-height:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{width:100%}body.fixed{overflow:hidden;position:fixed;width:100vw;height:100vh}i{font-style:normal}a{word-wrap:break-word;-webkit-tap-highlight-color:rgba(0,0,0,0)}a:hover{text-decoration:none}.fade-enter-active,.fade-leave-active{transition:opacity .3s}.fade-enter,.fade-leave-to{opacity:0}.MathJax,.MathJax_CHTML,.MathJax_MathContainer,.MathJax_MathML,.MathJax_PHTML,.MathJax_PlainSource,.MathJax_SVG{outline:0}.ios-app-switch .js-audit{display:none}._loading_wrap_{position:fixed;width:100vw;height:100vh;top:50%;left:50%;transform:translate(-50%,-50%);z-index:999}._loading_div_class_,._loading_wrap_{display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;-ms-flex-align:center;align-items:center}._loading_div_class_{word-wrap:break-word;padding:.5rem .75rem;text-align:center;z-index:9999;font-size:.6rem;max-width:60%;color:#fff;border-radius:.25rem;-ms-flex-direction:column;flex-direction:column}._loading_div_class_ .message{color:#353535;font-size:16px;line-height:3}.spinner{animation:circle-rotator 1.4s linear infinite}.spinner *{line-height:0;box-sizing:border-box}@keyframes circle-rotator{0%{transform:rotate(0deg)}to{transform:rotate(270deg)}}.path{stroke-dasharray:187;stroke-dashoffset:0;transform-origin:center;animation:circle-dash 1.4s ease-in-out infinite,circle-colors 5.6s ease-in-out infinite}@keyframes circle-colors{0%{stroke:#ff5a05}to{stroke:#ff5a05}}@keyframes circle-dash{0%{stroke-dashoffset:187}50%{stroke-dashoffset:46.75;transform:rotate(135deg)}to{stroke-dashoffset:187;transform:rotate(450deg)}}.confirm-box-wrapper,.confirm-box-wrapper .mask{position:absolute;top:0;left:0;right:0;bottom:0}.confirm-box-wrapper .mask{background:rgba(0,0,0,.6)}.confirm-box-wrapper .confirm-box{position:fixed;top:50%;left:50%;width:267px;background:#fff;transform:translate(-50%,-50%);border-radius:7px}.confirm-box-wrapper .confirm-box .head{margin:0 18px;font-size:18px;text-align:center;line-height:65px;border-bottom:1px solid #d9d9d9}.confirm-box-wrapper .confirm-box .body{padding:18px;padding-bottom:0;color:#353535;font-size:12.5px;max-height:150px;overflow:auto}.confirm-box-wrapper .confirm-box .foot{display:-ms-flexbox;display:flex;-ms-flex-direction:row;flex-direction:row;padding:18px}.confirm-box-wrapper .confirm-box .foot .button-cancel{border:1px solid #d9d9d9}.hljs{display:block;overflow-x:auto;padding:.5em;color:#333;background:#f8f8f8}.hljs-comment,.hljs-quote{color:#998;font-style:italic}.hljs-keyword,.hljs-selector-tag,.hljs-subst{color:#333;font-weight:700}.hljs-literal,.hljs-number,.hljs-tag .hljs-attr,.hljs-template-variable,.hljs-variable{color:teal}.hljs-doctag,.hljs-string{color:#d14}.hljs-section,.hljs-selector-id,.hljs-title{color:#900;font-weight:700}.hljs-subst{font-weight:400}.hljs-class .hljs-title,.hljs-type{color:#458;font-weight:700}.hljs-attribute,.hljs-name,.hljs-tag{color:navy;font-weight:400}.hljs-link,.hljs-regexp{color:#009926}.hljs-bullet,.hljs-symbol{color:#990073}.hljs-built_in,.hljs-builtin-name{color:#0086b3}.hljs-meta{color:#999;font-weight:700}.hljs-deletion{background:#fdd}.hljs-addition{background:#dfd}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}




    </style>
    <style type="text/css">
        .button-cancel[data-v-87ffcada]{color:#888;border:1px solid #888;border-radius:3px;margin-right:12px}.button-cancel[data-v-87ffcada],.button-primary[data-v-87ffcada]{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;height:35px;display:inline-block;font-size:15px;text-align:center;line-height:36px}.button-primary[data-v-87ffcada]{color:#fff;background-color:#ff5a05;border-radius:3px}.pd[data-v-87ffcada]{padding-left:1.375rem;padding-right:1.375rem}.article[data-v-87ffcada]{max-width:70rem;margin:0 auto}.article .article-unavailable[data-v-87ffcada]{color:#fa8919;font-size:15px;font-weight:600;line-height:24px;border-radius:5px;padding:12px;background-color:#f6f7fb;margin-top:20px}.article .article-unavailable .iconfont[data-v-87ffcada]{font-size:12px}.article .main[data-v-87ffcada]{padding:1.25rem 0;margin-bottom:52px}.article-title[data-v-87ffcada]{color:#353535;font-weight:400;line-height:1.65rem;font-size:1.34375rem}.article-info[data-v-87ffcada]{color:#888;font-size:.9375rem;margin-top:1.0625rem}.article-content[data-v-87ffcada]{margin-top:1.0625rem}.article-content.android video[data-v-87ffcada]::-webkit-media-controls-fullscreen-button{display:none}.copyright[data-v-87ffcada]{color:#b2b2b2;padding-bottom:20px;margin-top:20px;font-size:13px}.audio-player[data-v-87ffcada]{width:100%;margin:20px 0}.to-comment[data-v-87ffcada]{overflow:hidden;padding-top:10px;margin-bottom:-30px}.to-comment a.button-primary[data-v-87ffcada]{float:right;height:20px;font-size:12px;line-height:20px;padding:4px 8px;cursor:pointer}.article-comments[data-v-87ffcada]{margin-top:2rem}.article-comments h2[data-v-87ffcada]{text-align:center;color:#888;position:relative;z-index:1;margin-bottom:1rem}.article-comments h2[data-v-87ffcada]:before{border-top:1px dotted #888;content:"";position:absolute;top:56%;left:0;width:100%;z-index:-1}.article-comments h2 span[data-v-87ffcada]{font-size:15.25px;font-weight:400;padding:0 1rem;background:#fff;display:inline-block}.article-sub-bottom[data-v-87ffcada]{z-index:10;cursor:pointer}.switch-btns[data-v-87ffcada]{height:76px;cursor:pointer;padding-top:24px;padding-bottom:24px;border-bottom:10px solid #f6f7fb;position:relative}.switch-btns[data-v-87ffcada]:before{content:" ";height:1px;background:#e8e8e8;position:absolute;top:0;left:0;-webkit-box-sizing:border-box;box-sizing:border-box;left:1.375rem;right:1.375rem}.switch-btns .btn[data-v-87ffcada]{height:38px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.switch-btns .btn .tag[data-v-87ffcada]{-webkit-box-flex:0;-ms-flex:0 0 62px;flex:0 0 62px;text-align:center;color:#888;font-size:14px;border-radius:10px;height:22px;line-height:22px;background:#f6f7fb;font-weight:400}.switch-btns .btn .txt[data-v-87ffcada]{margin-left:10px;-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;color:#888;font-size:15px;height:22px;line-height:22px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:400}@media (max-width:769px){.article .breadcrumb[data-v-87ffcada]{padding-top:10px;padding-bottom:10px}}





    </style>

    <style type="text/css">
        .comment-item{list-style-position:inside;width:100%;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;margin-bottom:1rem}.comment-item a{border-bottom:none}.comment-item .avatar{width:2.625rem;height:2.625rem;-ms-flex-negative:0;flex-shrink:0;border-radius:50%}.comment-item .info{margin-left:.5rem;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1}.comment-item .info .hd{width:100%;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.comment-item .info .hd .username{color:#888;font-size:15.25px;font-weight:400;line-height:1.2}.comment-item .info .hd .control{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.comment-item .info .hd .control .btn-share{color:#888;font-size:.75rem;margin-right:1rem}.comment-item .info .hd .control .btn-praise{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-webkit-box-align:center;-ms-flex-align:center;align-items:center;font-size:15.25px;text-decoration:none}.comment-item .info .hd .control .btn-praise i{color:#888;display:inline-block;font-size:.75rem;margin-right:.3rem;margin-top:-.01rem}.comment-item .info .hd .control .btn-praise i.on,.comment-item .info .hd .control .btn-praise span{color:#ff5a05}.comment-item .info .bd{color:#353535;font-size:15.25px;font-weight:400;white-space:normal;word-break:break-all;line-height:1.6}.comment-item .info .time{color:#888;font-size:9px;line-height:1}.comment-item .info .reply .reply-hd{font-size:15.25px}.comment-item .info .reply .reply-hd span{margin-left:-12px;color:#888;font-weight:400}.comment-item .info .reply .reply-hd i{color:#ff5a05;font-size:15.25px}.comment-item .info .reply .reply-content{color:#353535;font-size:15.25px;font-weight:400;white-space:normal;word-break:break-all}.comment-item .info .reply .reply-time{color:#888;font-size:9px}




    </style>
</head>
<body>
<div id="app">


    <div data-v-87ffcada="" class="article" id="watermark">
        <p class="x">更多课程请加QQ群170701297</p>
        <div data-v-87ffcada="" class="main main-app">
            <h1 data-v-87ffcada="" class="article-title pd">
                28讲案例篇：一个SQL查询要15秒，这是怎么回事
            </h1>
            <div data-v-87ffcada="" class="article-content typo common-content pd"><img data-v-87ffcada=""
                                                                                        src="https://static001.geekbang.org/resource/image/f3/91/f388b9e4eb89333f72054a747e65e391.jpg">


                <div data-v-87ffcada="" id="article-content" class="">
                    <div class="text">
                        <p>你好，我是倪朋飞。</p><p>上一节，我们分析了一个单词热度应用响应过慢的案例。当用 top、iostat 分析了系统的 CPU 和磁盘 I/O 使用情况后，我们发现系统出现了磁盘的 I/O 瓶颈，而且正是案例应用导致的。</p><p>接着，在使用 strace 却没有任何发现后，我又给你介绍了两个新的工具 filetop 和 opensnoop，分析它们对系统调用 write() 和 open() 的追踪结果。</p><p>我们发现，案例应用正在读写大量的临时文件，因此产生了性能瓶颈。找出瓶颈后，我们又用把文件数据都放在内存的方法，解决了磁盘 I/O 的性能问题。</p><p>当然，你可能会说，在实际应用中，大量数据肯定是要存入数据库的，而不会直接用文本文件的方式存储。不过，数据库也不是万能的。当数据库出现性能问题时，又该如何分析和定位它的瓶颈呢？</p><p>今天我们就来一起分析一个数据库的案例。这是一个基于 Python Flask 的商品搜索应用，商品信息存在 MySQL 中。这个应用可以通过 MySQL 接口，根据客户端提供的商品名称，去数据库表中查询商品信息。</p><p>非常感谢唯品会资深运维工程师阳祥义，帮助提供了今天的案例。</p><h2>案例准备</h2><p>本次案例还是基于 Ubuntu 18.04，同样适用于其他的 Linux 系统。我使用的案例环境如下所示：</p><!-- [[[read_end]]] --><ul>
<li>
<p>机器配置：2 CPU，8GB 内存</p>
</li>
<li>
<p>预先安装 docker、sysstat 、git、make 等工具，如 apt install <a href="http://docker.io">docker.io</a> sysstat make git</p>
</li>
</ul><p>其中，docker 和 sysstat 已经用过很多次，这里不再赘述；git 用来拉取本次案例所需脚本，这些脚本存储在 Github 代码仓库中；最后的 make 则是一个常用构建工具，这里用来运行今天的案例。</p><p>案例总共由三个容器组成，包括一个 MySQL 数据库应用、一个商品搜索应用以及一个数据处理的应用。其中，商品搜索应用以 HTTP 的形式提供了一个接口：</p><ul>
<li>
<p>/：返回 Index Page；</p>
</li>
<li>
<p>/db/insert/products/<num>：插入指定数量的商品信息；</num></p>
</li>
<li>
<p>/products/<product>：查询指定商品的信息，并返回处理时间。</product></p>
</li>
</ul><p>由于应用比较多，为了方便你运行它们，我把它们同样打包成了几个 Docker 镜像，并推送到了 Github 上。这样，你只需要运行几条命令，就可以启动了。</p><p>今天的案例需要两台虚拟机，其中一台作为案例分析的目标机器，运行 Flask 应用，它的 IP 地址是 192.168.0.10；另一台则是作为客户端，请求单词的热度。我画了一张图表示它们的关系。</p><p><img src="https://static001.geekbang.org/resource/image/8c/5d/8c954570f6e46193505c2598a06cbc5d.png" alt=""></p><p>接下来，打开两个终端，分别 SSH 登录到这两台虚拟机中，并在第一台虚拟机中安装上述工具。</p><p>跟以前一样，案例中所有命令都默认以 root 用户运行，如果你是用普通用户身份登陆系统，请运行 sudo su root命令切换到 root 用户。</p><p>到这里，准备工作就完成了。接下来，我们正式进入操作环节。</p><h2>案例分析</h2><p>首先，我们在第一个终端中执行下面命令，拉取本次案例所需脚本：</p><pre><code>$ git clone https://github.com/feiskyer/linux-perf-examples
$ cd linux-perf-examples/mysql-slow
</code></pre><p>接着，执行下面的命令，运行本次的目标应用。正常情况下，你应该可以看到下面的输出：</p><pre><code># 注意下面的随机字符串是容器ID，每次运行均会不同，并且你不需要关注它，因为我们只会用到名字
$ make run
docker run --name=mysql -itd -p 10000:80 -m 800m feisky/mysql:5.6
WARNING: Your kernel does not support swap limit capabilities or the cgroup is not mounted. Memory limited without swap.
4156780da5be0b9026bcf27a3fa56abc15b8408e358fa327f472bcc5add4453f
docker run --name=dataservice -itd --privileged feisky/mysql-dataservice
f724d0816d7e47c0b2b1ff701e9a39239cb9b5ce70f597764c793b68131122bb
docker run --name=app --network=container:mysql -itd feisky/mysql-slow
81d3392ba25bb8436f6151662a13ff6182b6bc6f2a559fc2e9d873cd07224ab6
</code></pre><p>然后，再运行 docker ps 命令，确认三个容器都处在运行（Up）状态：</p><pre><code>$ docker ps
CONTAINER ID        IMAGE                      COMMAND                  CREATED             STATUS              PORTS                             NAMES
9a4e3c580963        feisky/mysql-slow          &quot;python /app.py&quot;         42 seconds ago      Up 36 seconds                                         app
2a47aab18082        feisky/mysql-dataservice   &quot;python /dataservice…&quot;   46 seconds ago      Up 41 seconds                                         dataservice
4c3ff7b24748        feisky/mysql:5.6           &quot;docker-entrypoint.s…&quot;   47 seconds ago      Up 46 seconds       3306/tcp, 0.0.0.0:10000-&gt;80/tcp   mysql
</code></pre><p>MySQL 数据库的启动过程，需要做一些初始化工作，这通常需要花费几分钟时间。你可以运行 docker logs 命令，查看它的启动过程。</p><p>当你看到下面这个输出时，说明 MySQL 初始化完成，可以接收外部请求了：</p><pre><code>$ docker logs -f mysql
...
... [Note] mysqld: ready for connections.
Version: '5.6.42-log'  socket: '/var/run/mysqld/mysqld.sock'  port: 3306  MySQL Community Server (GPL)
</code></pre><p>而商品搜索应用则是在 10000 端口监听。你可以按 Ctrl+C ，停止 docker logs 命令；然后，执行下面的命令，确认它也已经正常运行。如果一切正常，你会看到 Index Page 的输出：</p><pre><code>$ curl http://127.0.0.1:10000/
Index Page
</code></pre><p>接下来，运行 make init 命令，初始化数据库，并插入 10000 条商品信息。这个过程比较慢，比如在我的机器中，就花了十几分钟时间。耐心等待一段时间后，你会看到如下的输出：</p><pre><code>$ make init
docker exec -i mysql mysql -uroot -P3306 &lt; tables.sql
curl http://127.0.0.1:10000/db/insert/products/10000
insert 10000 lines
</code></pre><p>接着，我们切换到第二个终端，访问一下商品搜索的接口，看看能不能找到想要的商品。执行如下的 curl 命令：</p><pre><code>$ curl http://192.168.0.10:10000/products/geektime
Got data: () in 15.364538192749023 sec
</code></pre><p>稍等一会儿，你会发现，这个接口返回的是空数据，而且处理时间超过15 秒。这么慢的响应速度让人无法忍受，到底出了什么问题呢？</p><p>既然今天用了 MySQL，你估计会猜到是慢查询的问题。</p><p>不过别急，在具体分析前，为了避免在分析过程中客户端的请求结束，我们把 curl 命令放到一个循环里执行。同时，为了避免给系统过大压力，我们设置在每次查询后，都先等待 5 秒，然后再开始新的请求。</p><p>所以，你可以在终端二中，继续执行下面的命令：</p><pre><code>$ while true; do curl http://192.168.0.10:10000/products/geektime; sleep 5; done
</code></pre><p>接下来，重新回到终端一中，分析接口响应速度慢的原因。不过，重回终端一后，你会发现系统响应也明显变慢了，随便执行一个命令，都得停顿一会儿才能看到输出。</p><p>这跟上一节的现象很类似，看来，我们还是得观察一下系统的资源使用情况，比如CPU、内存和磁盘 I/O 等的情况。</p><p>首先，我们在终端一执行 top 命令，分析系统的 CPU 使用情况：</p><pre><code>$ top
top - 12:02:15 up 6 days,  8:05,  1 user,  load average: 0.66, 0.72, 0.59
Tasks: 137 total,   1 running,  81 sleeping,   0 stopped,   0 zombie
%Cpu0  :  0.7 us,  1.3 sy,  0.0 ni, 35.9 id, 62.1 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu1  :  0.3 us,  0.7 sy,  0.0 ni, 84.7 id, 14.3 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  8169300 total,  7238472 free,   546132 used,   384696 buff/cache
KiB Swap:        0 total,        0 free,        0 used.  7316952 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
27458 999       20   0  833852  57968  13176 S   1.7  0.7   0:12.40 mysqld
27617 root      20   0   24348   9216   4692 S   1.0  0.1   0:04.40 python
 1549 root      20   0  236716  24568   9864 S   0.3  0.3  51:46.57 python3
22421 root      20   0       0      0      0 I   0.3  0.0   0:01.16 kworker/u
</code></pre><p>观察 top 的输出，我们发现，两个 CPU 的 iowait 都比较高，特别是 CPU0，iowait 已经超过 60%。而具体到各个进程， CPU 使用率并不高，最高的也只有 1.7%。</p><p>既然CPU的嫌疑不大，那问题应该还是出在了 I/O 上。我们仍然在第一个终端，按下 Ctrl+C，停止 top 命令；然后，执行下面的 iostat 命令，看看有没有 I/O 性能问题：</p><pre><code>$ iostat -d -x 1
Device            r/s     w/s     rkB/s     wkB/s   rrqm/s   wrqm/s  %rrqm  %wrqm r_await w_await aqu-sz rareq-sz wareq-sz  svctm  %util
...
sda            273.00    0.00  32568.00      0.00     0.00     0.00   0.00   0.00    7.90    0.00   1.16   119.30     0.00   3.56  97.20
</code></pre><p>iostat 的输出你应该非常熟悉。观察这个界面，我们发现，磁盘 sda 每秒的读数据为 32 MB， 而 I/O 使用率高达 97% ，接近饱和，这说明，磁盘 sda 的读取确实碰到了性能瓶颈。</p><p>那要怎么知道，这些 I/O请求到底是哪些进程导致的呢？当然可以找我们的老朋友， pidstat。接下来，在终端一中，按下 Ctrl+C 停止 iostat 命令，然后运行下面的 pidstat 命令，观察进程的 I/O 情况：</p><pre><code># -d选项表示展示进程的I/O情况
$ pidstat -d 1
12:04:11      UID       PID   kB_rd/s   kB_wr/s kB_ccwr/s iodelay  Command
12:04:12      999     27458  32640.00      0.00      0.00       0  mysqld
12:04:12        0     27617      4.00      4.00      0.00       3  python
12:04:12        0     27864      0.00      4.00      0.00       0  systemd-journal
</code></pre><p>从 pidstat 的输出可以看到，PID 为 27458 的 mysqld 进程正在进行大量的读，而且读取速度是 32 MB/s，跟刚才 iostat 的发现一致。两个结果一对比，我们自然就找到了磁盘 I/O 瓶颈的根源，即 mysqld 进程。</p><p>不过，这事儿还没完。我们自然要怀疑一下，为什么 mysqld 会去读取大量的磁盘数据呢？按照前面猜测，我们提到过，这有可能是个慢查询问题。</p><p>可是，回想一下，慢查询的现象大多是 CPU 使用率高（比如 100% ），但这里看到的却是 I/O 问题。看来，这并不是一个单纯的慢查询问题，我们有必要分析一下 MySQL 读取的数据。</p><p>要分析进程的数据读取，当然还要靠上一节用到过的 strace+ lsof 组合。</p><p>接下来，还是在终端一中，执行 strace 命令，并且指定 mysqld 的进程号 27458。我们知道，MySQL 是一个多线程的数据库应用，为了不漏掉这些线程的数据读取情况，你要记得在执行 stace 命令时，加上 -f 参数：</p><pre><code>$ strace -f -p 27458
[pid 28014] read(38, &quot;934EiwT363aak7VtqF1mHGa4LL4Dhbks&quot;..., 131072) = 131072
[pid 28014] read(38, &quot;hSs7KBDepBqA6m4ce6i6iUfFTeG9Ot9z&quot;..., 20480) = 20480
[pid 28014] read(38, &quot;NRhRjCSsLLBjTfdqiBRLvN9K6FRfqqLm&quot;..., 131072) = 131072
[pid 28014] read(38, &quot;AKgsik4BilLb7y6OkwQUjjqGeCTQTaRl&quot;..., 24576) = 24576
[pid 28014] read(38, &quot;hFMHx7FzUSqfFI22fQxWCpSnDmRjamaW&quot;..., 131072) = 131072
[pid 28014] read(38, &quot;ajUzLmKqivcDJSkiw7QWf2ETLgvQIpfC&quot;..., 20480) = 20480
</code></pre><p>观察一会，你会发现，线程 28014 正在读取大量数据，且读取文件的描述符编号为 38。这儿的 38 又对应着哪个文件呢？我们可以执行下面的 lsof 命令，并且指定线程号 28014 ，具体查看这个可疑线程和可疑文件：</p><pre><code>$ lsof -p 28014
</code></pre><p>奇怪的是，lsof 并没有给出任何输出。实际上，如果你查看 lsof 命令的返回值，就会发现，这个命令的执行失败了。</p><p>我们知道，在 SHELL 中，特殊标量 $? 表示上一条命令退出时的返回值。查看这个特殊标量，你会发现它的返回值是1。可是别忘了，在 Linux 中，返回值为 0 ，才表示命令执行成功。返回值为1，显然表明执行失败。</p><pre><code>$ echo $?
1
</code></pre><p>为什么 lsof 命令执行失败了呢？这里希望你暂停往下，自己先思考一下原因。记住我的那句话，遇到现象解释不了，先去查查工具文档。</p><p>事实上，通过查询 lsof 的文档，你会发现，-p 参数需要指定进程号，而我们刚才传入的是线程号，所以 lsof 失败了。你看，任何一个细节都可能成为性能分析的“拦路虎”。</p><p>回过头我们看，mysqld 的进程号是 27458，而 28014 只是它的一个线程。而且，如果你观察 一下mysqld 进程的线程，你会发现，mysqld 其实还有很多正在运行的其他线程：</p><pre><code># -t表示显示线程，-a表示显示命令行参数
$ pstree -t -a -p 27458
mysqld,27458 --log_bin=on --sync_binlog=1
...
  ├─{mysqld},27922
  ├─{mysqld},27923
  └─{mysqld},28014
</code></pre><p>找到了原因，lsof的问题就容易解决了。把线程号换成进程号，继续执行 lsof 命令：</p><pre><code>$ lsof -p 27458
COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
...
​mysqld  27458      999   38u   REG    8,1 512440000 2601895 /var/lib/mysql/test/products.MYD
</code></pre><p>这次我们得到了lsof的输出。从输出中可以看到， mysqld 进程确实打开了大量文件，而根据文件描述符（FD）的编号，我们知道，描述符为 38 的是一个路径为 /var/lib/mysql/test/products.MYD 的文件。这里注意， 38 后面的 u 表示， mysqld 以读写的方式访问文件。</p><p>看到这个文件，熟悉 MySQL 的你可能笑了：</p><ul>
<li>
<p>MYD 文件，是 MyISAM 引擎用来存储表数据的文件；</p>
</li>
<li>
<p>文件名就是数据表的名字；</p>
</li>
<li>
<p>而这个文件的父目录，也就是数据库的名字。</p>
</li>
</ul><p>换句话说，这个文件告诉我们，mysqld 在读取数据库 test 中的 products 表。</p><p>实际上，你可以执行下面的命令，查看 mysqld 在管理数据库 test 时的存储文件。不过要注意，由于 MySQL 运行在容器中，你需要通过 docker exec 到容器中查看：</p><pre><code>$ docker exec -it mysql ls /var/lib/mysql/test/
db.opt    products.MYD  products.MYI  products.frm
</code></pre><p>从这里你可以发现，/var/lib/mysql/test/ 目录中有四个文件，每个文件的作用分别是：</p><ul>
<li>
<p>MYD 文件用来存储表的数据；</p>
</li>
<li>
<p>MYI 文件用来存储表的索引；</p>
</li>
<li>
<p>frm 文件用来存储表的元信息（比如表结构）；</p>
</li>
<li>
<p>opt 文件则用来存储数据库的元信息（比如字符集、字符校验规则等）。</p>
</li>
</ul><p>当然，看到这些，你可能还有一个疑问，那就是，这些文件到底是不是 mysqld 正在使用的数据库文件呢？有没有可能是不再使用的旧数据呢？其实，这个很容易确认，查一下 mysqld 配置的数据路径即可。</p><p>你可以在终端一中，继续执行下面的命令：</p><pre><code>$ docker exec -i -t mysql mysql -e 'show global variables like &quot;%datadir%&quot;;'
+---------------+-----------------+
| Variable_name | Value           |
+---------------+-----------------+
| datadir       | /var/lib/mysql/ |
+---------------+-----------------+
</code></pre><p>这里可以看到，/var/lib/mysql/ 确实是 mysqld 正在使用的数据存储目录。刚才分析得出的数据库 test 和数据表 products ，都是正在使用。</p><blockquote>
<p>注：其实 lsof 的结果已经可以确认，它们都是 mysqld 正在访问的文件。再查询 datadir ，只是想换一个思路，进一步确认一下。</p>
</blockquote><p>既然已经找出了数据库和表，接下来要做的，就是弄清楚数据库中正在执行什么样的 SQL 了。我们继续在终端一中，运行下面的 docker exec 命令，进入 MySQL 的命令行界面：</p><pre><code>$ docker exec -i -t mysql mysql
...
​
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
​
mysql&gt;
</code></pre><p>下一步你应该可以想到，那就是在 MySQL 命令行界面中，执行 show processlist 命令，来查看当前正在执行的 SQL 语句。</p><p>不过，为了保证 SQL 语句不截断，这里我们可以执行 show full processlist 命令。如果一切正常，你应该可以看到如下输出：</p><pre><code>mysql&gt; show full processlist;
+----+------+-----------------+------+---------+------+--------------+-----------------------------------------------------+
| Id | User | Host            | db   | Command | Time | State        | Info                                                |
+----+------+-----------------+------+---------+------+--------------+-----------------------------------------------------+
| 27 | root | localhost       | test | Query   |    0 | init         | show full processlist                               |
| 28 | root | 127.0.0.1:42262 | test | Query   |    1 | Sending data | select * from products where productName='geektime' |
+----+------+-----------------+------+---------+------+--------------+-----------------------------------------------------+
2 rows in set (0.00 sec)
</code></pre><p>这个输出中，</p><ul>
<li>
<p>db 表示数据库的名字；</p>
</li>
<li>
<p>Command 表示 SQL 类型；</p>
</li>
<li>
<p>Time 表示执行时间；</p>
</li>
<li>
<p>State 表示状态；</p>
</li>
<li>
<p>而 Info 则包含了完整的 SQL 语句。</p>
</li>
</ul><p>多执行几次 show full processlist 命令，你可看到 select * from products where productName=‘geektime’ 这条 SQL 语句的执行时间比较长。</p><p>再回忆一下，案例开始时，我们在终端二查询的产品名称 <a href="http://192.168.0.10:10000/products/geektime">http://192.168.0.10:10000/products/geektime</a>，其中的 geektime 也符合这条查询语句的条件。</p><p>我们知道，MySQL 的慢查询问题，很可能是没有利用好索引导致的，那这条查询语句是不是这样呢？我们又该怎么确认，查询语句是否利用了索引呢？</p><p>其实，MySQL 内置的 explain 命令，就可以帮你解决这个问题。继续在 MySQL 终端中，运行下面的 explain 命令：</p><pre><code># 切换到test库
mysql&gt; use test;
# 执行explain命令
mysql&gt; explain select * from products where productName='geektime';
+----+-------------+----------+------+---------------+------+---------+------+-------+-------------+
| id | select_type | table    | type | possible_keys | key  | key_len | ref  | rows  | Extra       |
+----+-------------+----------+------+---------------+------+---------+------+-------+-------------+
|  1 | SIMPLE      | products | ALL  | NULL          | NULL | NULL    | NULL | 10000 | Using where |
+----+-------------+----------+------+---------------+------+---------+------+-------+-------------+
1 row in set (0.00 sec)
</code></pre><p>观察这次的输出。这个界面中，有几个比较重要的字段需要你注意，我就以这个输出为例，分别解释一下：</p><ul>
<li>
<p>select_type 表示查询类型，而这里的SIMPLE 表示此查询不包括 UNION 查询或者子查询；</p>
</li>
<li>
<p>table 表示数据表的名字，这里是 products；</p>
</li>
<li>
<p>type 表示查询类型，这里的 ALL 表示全表查询，但索引查询应该是 index 类型才对；</p>
</li>
<li>
<p>possible_keys 表示可能选用的索引，这里是 NULL；</p>
</li>
<li>
<p>key 表示确切会使用的索引，这里也是 NULL；</p>
</li>
<li>
<p>rows 表示查询扫描的行数，这里是 10000。</p>
</li>
</ul><p>根据这些信息，我们可以确定，这条查询语句压根儿没有使用索引，所以查询时，会扫描全表，并且扫描行数高达 10000 行。响应速度那么慢也就难怪了。</p><p>走到这一步，你应该很容易想到优化方法，没有索引那我们就自己建立，给 productName 建立索引就可以了。不过，增加索引前，你需要先弄清楚，这个表结构到底长什么样儿。</p><p>执行下面的 MySQL 命令，查询 products 表的结构，你会看到，它只有一个 id 主键，并不包括 productName 的索引：</p><pre><code>mysql&gt; show create table products;
...
| products | CREATE TABLE `products` (
  `id` int(11) NOT NULL,
  `productCode` text NOT NULL COMMENT '产品代码',
  `productName` text NOT NULL COMMENT '产品名称',
...
  PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC |
...
</code></pre><p>接下来，我们就可以给 productName 建立索引了，也就是执行下面的 CREATE INDEX 命令：</p><pre><code>mysql&gt; CREATE INDEX products_index ON products (productName);
ERROR 1170 (42000): BLOB/TEXT column 'productName' used in key specification without a key length
</code></pre><p>不过，醒目的ERROR告诉我们，这条命令运行失败了。根据错误信息，productName 是一个 BLOB/TEXT 类型，需要设置一个长度。所以，想要创建索引，就必须为 productName 指定一个前缀长度。</p><p>那前缀长度设置为多大比较合适呢？这里其实有专门的算法，即通过计算前缀长度的选择性，来确定索引的长度。不过，我们可以稍微简化一下，直接使用一个固定数值（比如 64），执行下面的命令创建索引：</p><pre><code>mysql&gt; CREATE INDEX products_index ON products (productName(64));
Query OK, 10000 rows affected (14.45 sec)
Records: 10000  Duplicates: 0  Warnings: 0
</code></pre><p>现在可以看到，索引已经建好了。能做的都做完了，最后就该检查一下，性能问题是否已经解决了。</p><p>我们切换到终端二中，查看还在执行的 curl 命令的结果：</p><pre><code>Got data: ()in 15.383180141448975 sec
Got data: ()in 15.384996891021729 sec
Got data: ()in 0.0021054744720458984 sec
Got data: ()in 0.003951072692871094 sec
</code></pre><p>显然，查询时间已经从 15 秒缩短到了 3 毫秒。看来，没有索引果然就是这次性能问题的罪魁祸首，解决了索引，就解决了查询慢的问题。</p><h2>案例思考</h2><p>到这里，商品搜索应用查询慢的问题已经完美解决了。但是，对于这个案例，我还有一点想说明一下。</p><p>不知道你还记不记得，案例开始时，我们启动的几个容器应用。除了 MySQL 和商品搜索应用外，还有一个 DataService 应用。为什么这个案例开始时，要运行一个看起来毫不相关的应用呢？</p><p>实际上，DataService 是一个严重影响 MySQL 性能的干扰应用。抛开上述索引优化方法不说，这个案例还有一种优化方法，也就是停止 DataService 应用。</p><p>接下来，我们就删除数据库索引，回到原来的状态；然后停止 DataService 应用，看看优化效果如何。</p><p>首先，我们在终端二中停止 curl 命令，然后回到终端一中，执行下面的命令删除索引：</p><pre><code># 删除索引
$ docker exec -i -t mysql mysql
​
mysql&gt; use test;
mysql&gt; DROP INDEX products_index ON products;
</code></pre><p>接着，在终端二中重新运行 curl 命令。当然，这次你会发现，处理时间又变慢了：</p><pre><code>$ while true; do curl http://192.168.0.10:10000/products/geektime; sleep 5; done
Got data: ()in 16.884345054626465 sec
</code></pre><p>接下来，再次回到终端一中，执行下面的命令，停止 DataService 应用：</p><pre><code># 停止 DataService 应用
$ docker rm -f dataservice
</code></pre><p>最后，我们回到终端二中，观察 curl 的结果：</p><pre><code>Got data: ()in 16.884345054626465 sec
Got data: ()in 15.238174200057983 sec
Got data: ()in 0.12604427337646484 sec
Got data: ()in 0.1101069450378418 sec
Got data: ()in 0.11235237121582031 sec
</code></pre><p>果然，停止 DataService 后，处理时间从 15 秒缩短到了 0.1 秒，虽然比不上增加索引后的 3 毫秒，但相对于 15 秒来说，优化效果还是非常明显的。</p><p>那么，这种情况下，还有没有 I/O 瓶颈了呢？</p><p>我们切换到终端一中，运行下面的 vmstat 命令（注意不是 iostat，稍后解释原因），观察 I/O 的变化情况：</p><pre><code>$ vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 0  1      0 6809304   1368 856744    0    0 32640     0   52  478  1  0 50 49  0
 0  1      0 6776620   1368 889456    0    0 32640     0   33  490  0  0 50 49  0
 0  0      0 6747540   1368 918576    0    0 29056     0   42  568  0  0 56 44  0
 0  0      0 6747540   1368 918576    0    0     0     0   40  141  1  0 100  0  0
 0  0      0 6747160   1368 918576    0    0     0     0   40  148  0  1 99  0  0
</code></pre><p>你可以看到，磁盘读（bi）和 iowait（wa）刚开始还是挺大的，但没过多久，就都变成了 0 。换句话说，I/O 瓶颈消失了。</p><p>这是为什么呢？原因先留个悬念，作为今天的思考题。</p><p>回过头来解释一下刚刚的操作，在查看 I/O 情况时，我并没用 iostat 命令，而是用了 vmstat。其实，相对于 iostat 来说，vmstat 可以同时提供 CPU、内存和 I/O 的使用情况。</p><p>在性能分析过程中，能够综合多个指标，并结合系统的工作原理进行分析，对解释性能现象通常会有意想不到的帮助。</p><h2>小结</h2><p>今天我们分析了一个商品搜索的应用程序。我们先是通过 top、iostat 分析了系统的 CPU 和磁盘使用情况，发现了磁盘的 I/O 瓶颈。</p><p>接着，我们借助 pidstat ，发现瓶颈是 mysqld 导致的。紧接着，我们又通过 strace、lsof，找出了 mysqld 正在读的文件。同时，根据文件的名字和路径，我们找出了 mysqld 正在操作的数据库和数据表。综合这些信息，我们判断，这是一个没有利用索引导致的慢查询问题。</p><p>于是，我们登录到 MySQL 命令行终端，用数据库分析工具进行验证，发现 MySQL 查询语句访问的字段，果然没有索引。所以，增加索引，就可以解决案例的性能问题了。</p><h2>思考</h2><p>最后，给你留一个思考题，也是我在案例最后部分提到过的，停止 DataService 后，商品搜索应用的处理时间，从 15 秒缩短到了 0.1 秒。这是为什么呢？</p><p>我给个小小的提示。你可以先查看 <a href="http://dataservice.py">dataservice.py</a> 的<a href="https://github.com/feiskyer/linux-perf-examples/blob/master/mysql-slow/dataservice.py">源码</a>，你会发现，DataService 实际上是在读写一个仅包括 “data” 字符串的小文件。不过在读取文件前，它会先把 /proc/sys/vm/drop_caches 改成 1。</p><p>还记得这个操作有什么作用吗？如果不记得，可以用 man 查询 proc 文件系统的文档。</p><p>欢迎在留言区和我讨论，也欢迎把这篇文章分享给你的同事、朋友。我们一起在实战中演练，在交流中进步。</p><p><img src="https://static001.geekbang.org/resource/image/a3/e9/a396aed4116c2c989771c1295736abe9.jpg" alt=""></p>
                    </div>
                </div>

            </div>
            <div data-v-87ffcada="" class="article-comments pd"><h2 data-v-87ffcada=""><span
                    data-v-87ffcada="">精选留言</span></h2>
                <ul data-v-87ffcada="">
                    
                    <li data-v-87ffcada="" class="comment-item"><img
                            src="https://wx.qlogo.cn/mmopen/vi_32/PiajxSqBRaEKQMM4m7NHuicr55aRiblTSEWIYe0QqbpyHweaoAbG7j2v7UUElqqeP3Ihrm3UfDPDRb1Hv8LvPwXqA/132" class="avatar">
                        <div class="info">
                            <div class="hd"><span class="username">ninuxer</span>
                            </div>
                            <div class="bd">打卡day29<br>echo 1&gt;&#47;proc&#47;sys&#47;vm&#47;drop_caches表示释放pagecache，也就是文件缓存，而mysql读书的数据就是文件缓存，dataservice不停的释放文件缓存，就导致MySQL都无法利用磁盘缓存，也就慢了～ <br></div>
                            <span class="time">2019-01-23 08:20</span>
                            
                            <div class="reply">
                                <div class="reply-hd"><span>作者回复</span></div>
                                <p class="reply-content">赞，正解</p>
                                <p class="reply-time">2019-01-23 22:02</p>
                            </div>
                            
                        </div>
                    </li>
                    
                    <li data-v-87ffcada="" class="comment-item"><img
                            src="https://static001.geekbang.org/account/avatar/00/13/f8/70/f3a33a14.jpg" class="avatar">
                        <div class="info">
                            <div class="hd"><span class="username">某、人</span>
                            </div>
                            <div class="bd">非常赞，这篇案例很好的展示了怎么从操作系统层面去排查慢查询的问题 <br></div>
                            <span class="time">2019-01-24 16:57</span>
                            
                        </div>
                    </li>
                    
                    <li data-v-87ffcada="" class="comment-item"><img
                            src="https://static001.geekbang.org/account/avatar/00/13/d2/cc/d54b7e5b.jpg" class="avatar">
                        <div class="info">
                            <div class="hd"><span class="username">guoew</span>
                            </div>
                            <div class="bd">以前遇到数据库慢的问题都是直接进数据库show processlist ，没有一个循序渐进的过程。感谢老师 <br></div>
                            <span class="time">2019-01-25 09:32</span>
                            
                        </div>
                    </li>
                    
                    <li data-v-87ffcada="" class="comment-item"><img
                            src="http://thirdwx.qlogo.cn/mmopen/vi_32/xzHDjCSFicNY3MUMECtNz6sM8yDJhBoyGk5IRoOtUat6ZIkGzxjqEqwqKYWMD3GjehScKvMjicGOGDog5FF18oyg/132" class="avatar">
                        <div class="info">
                            <div class="hd"><span class="username">Geek_a9ec17</span>
                            </div>
                            <div class="bd">mysql打开慢查询日志，排查SQL性能问题更方便 <br></div>
                            <span class="time">2019-01-23 15:26</span>
                            
                            <div class="reply">
                                <div class="reply-hd"><span>作者回复</span></div>
                                <p class="reply-content">嗯嗯，慢查询推荐总是开启</p>
                                <p class="reply-time">2019-01-23 22:06</p>
                            </div>
                            
                        </div>
                    </li>
                    
                    <li data-v-87ffcada="" class="comment-item"><img
                            src="https://static001.geekbang.org/account/avatar/00/13/47/46/61f16147.jpg" class="avatar">
                        <div class="info">
                            <div class="hd"><span class="username">唯美</span>
                            </div>
                            <div class="bd">打卡Day27 <br></div>
                            <span class="time">2019-01-27 09:59</span>
                            
                        </div>
                    </li>
                    
                    <li data-v-87ffcada="" class="comment-item"><img
                            src="https://static001.geekbang.org/account/avatar/00/13/2f/f4/2dede51a.jpg" class="avatar">
                        <div class="info">
                            <div class="hd"><span class="username">小老鼠</span>
                            </div>
                            <div class="bd">即停止DataService ，又增加索3引是不是性能会更好？ <br></div>
                            <span class="time">2019-01-26 17:06</span>
                            
                            <div class="reply">
                                <div class="reply-hd"><span>作者回复</span></div>
                                <p class="reply-content">是的，利用缓存后性能会更好</p>
                                <p class="reply-time">2019-01-27 16:19</p>
                            </div>
                            
                        </div>
                    </li>
                    
                    <li data-v-87ffcada="" class="comment-item"><img
                            src="https://static001.geekbang.org/account/avatar/00/10/cf/5c/d4e19eb6.jpg" class="avatar">
                        <div class="info">
                            <div class="hd"><span class="username">安小依</span>
                            </div>
                            <div class="bd">老师好，上次留言说 Java 无法 strace 打日志的问题解决了，原来 strace 必须跟上 -f 选项才可以，最终定位到当疯狂 System.out.println 的时候，原来是写到 pipe 管道里边去了：<br><br>java    2794   zk    1w  FIFO               0,12      0t0  3626492 pipe<br><br>到这个地方还是理解不了，这个 pipe 最终是存放在哪里了呢？为什么疯狂打日志，会把磁盘打满呢？谢谢 <br></div>
                            <span class="time">2019-01-24 17:04</span>
                            
                            <div class="reply">
                                <div class="reply-hd"><span>作者回复</span></div>
                                <p class="reply-content">pipe 是一个管道，而不是文件，可以用 lsof 找一下 pipe 的对端进程</p>
                                <p class="reply-time">2019-01-27 16:09</p>
                            </div>
                            
                        </div>
                    </li>
                    
                    <li data-v-87ffcada="" class="comment-item"><img
                            src="" class="avatar">
                        <div class="info">
                            <div class="hd"><span class="username">划时代</span>
                            </div>
                            <div class="bd">打卡 <br></div>
                            <span class="time">2019-01-24 09:09</span>
                            
                        </div>
                    </li>
                    
                    <li data-v-87ffcada="" class="comment-item"><img
                            src="https://static001.geekbang.org/account/avatar/00/12/da/ec/779c1a78.jpg" class="avatar">
                        <div class="info">
                            <div class="hd"><span class="username">往事随风，顺其自然</span>
                            </div>
                            <div class="bd">centos下执行这个案例，不会出现查询慢的情况，很正常的结束 <br></div>
                            <span class="time">2019-01-23 20:55</span>
                            
                            <div class="reply">
                                <div class="reply-hd"><span>作者回复</span></div>
                                <p class="reply-content">增大初始化的数据量（比如增大10倍）再试试呢？</p>
                                <p class="reply-time">2019-01-27 16:02</p>
                            </div>
                            
                        </div>
                    </li>
                    
                    <li data-v-87ffcada="" class="comment-item"><img
                            src="https://static001.geekbang.org/account/avatar/00/12/da/ec/779c1a78.jpg" class="avatar">
                        <div class="info">
                            <div class="hd"><span class="username">往事随风，顺其自然</span>
                            </div>
                            <div class="bd">curl http:&#47;&#47;192.168.193.132:10000&#47;products&#47;geektime<br>Got data: () in 0.4080049991607666 sec <br>时间很快并没有出现长时间查询的情况 <br></div>
                            <span class="time">2019-01-23 20:29</span>
                            
                            <div class="reply">
                                <div class="reply-hd"><span>作者回复</span></div>
                                <p class="reply-content">看来你的系统配置比较高，增加下初始化时的数据量（比如增加10倍）再试试？</p>
                                <p class="reply-time">2019-01-27 16:01</p>
                            </div>
                            
                        </div>
                    </li>
                    
                    <li data-v-87ffcada="" class="comment-item"><img
                            src="https://static001.geekbang.org/account/avatar/00/12/da/ec/779c1a78.jpg" class="avatar">
                        <div class="info">
                            <div class="hd"><span class="username">往事随风，顺其自然</span>
                            </div>
                            <div class="bd">make run报错如下：<br>flag provided but not defined: --network<br>mysql-slow这个没法运行起来 <br></div>
                            <span class="time">2019-01-23 20:13</span>
                            
                            <div class="reply">
                                <div class="reply-hd"><span>作者回复</span></div>
                                <p class="reply-content">mysql 没有运行起来，运行 docker logs mysql 看看报了什么错误？</p>
                                <p class="reply-time">2019-01-27 15:56</p>
                            </div>
                            
                        </div>
                    </li>
                    
                    <li data-v-87ffcada="" class="comment-item"><img
                            src="https://static001.geekbang.org/account/avatar/00/10/41/27/3ff1a1d6.jpg" class="avatar">
                        <div class="info">
                            <div class="hd"><span class="username">hua168</span>
                            </div>
                            <div class="bd">老师，问3个很重要的题外问题：<br>1. 大专学历，想直接自学考本科或研究生，自考学历中大型公司承认的吗？<br>2. 大公司对年龄有限制的吗？<br>3. 30多岁，运维（编程自学java一年，没项目经验），只有几年小公司运维经验，技术一般，发展方向是什么？很多IT公司好像都不要年龄大点的~~人生80，那不是40岁就没得工作了？ <br></div>
                            <span class="time">2019-01-23 17:10</span>
                            
                            <div class="reply">
                                <div class="reply-hd"><span>作者回复</span></div>
                                <p class="reply-content">不用考虑那么多，我觉得打好基础，持续提升自身的能力，开放的心态接收新东西，工作不会那么容易丢的（即使丢了也很容易找到新的）</p>
                                <p class="reply-time">2019-01-23 22:13</p>
                            </div>
                            
                        </div>
                    </li>
                    
                    <li data-v-87ffcada="" class="comment-item"><img
                            src="https://static001.geekbang.org/account/avatar/00/14/0f/84/d8e63885.jpg" class="avatar">
                        <div class="info">
                            <div class="hd"><span class="username">仲鬼</span>
                            </div>
                            <div class="bd">老师好，我认为explain的结果已经能说明page cache未缓存MYD文件。<br><br>原因：<br>1. type为ALL表示不使用索引，全表查询；<br>2. keys为NULL表示不使用索引。<br>结论：该语句需要扫描全表。<br><br>补充MyISAM的一些重要特点：<br>1. 查询数据时，如果能命中索引，会先在索引中查询匹配行的位置，再去MYD文件中取数据。<br>2. MyISAM自己维护索引（MYI文件）的缓存，利用page cache维护数据（MYD文件）的缓存。<br><br>结合之前返回的空结果，特点1说明表中不存在匹配行。在这种情况下，按理说只查询索引应该很快能得到“空结果”，所以“响应慢”说明未建立相关索引。<br>但根据特点2，就算是未建立索引，在page cache中顺序读全表数据也不会这么慢，所以，特点2说明page cache未缓存MYD文件。 <br></div>
                            <span class="time">2019-01-23 12:11</span>
                            
                            <div class="reply">
                                <div class="reply-hd"><span>作者回复</span></div>
                                <p class="reply-content">第2条有可能也是很慢的，当然慢的程度取决于操作和数据量的大小</p>
                                <p class="reply-time">2019-01-23 22:04</p>
                            </div>
                            
                        </div>
                    </li>
                    
                    <li data-v-87ffcada="" class="comment-item"><img
                            src="https://static001.geekbang.org/account/avatar/00/11/86/9a/4231fb93.jpg" class="avatar">
                        <div class="info">
                            <div class="hd"><span class="username">AA</span>
                            </div>
                            <div class="bd">Device:         rrqm&#47;s   wrqm&#47;s     r&#47;s     w&#47;s    rMB&#47;s    wMB&#47;s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util<br>sda               0.00   160.00    0.00  618.00     0.00    12.86    42.60   144.44  224.37    0.00  224.37   1.62 100.00<br>dm-3              0.00     0.00    0.00    5.00     0.00     0.04    16.20     1.08  216.40    0.00  216.40  69.00  34.50<br>dm-5              0.00     0.00    0.00  761.00     0.00    11.83    31.84   203.54  283.46    0.00  283.46   1.31 100.00<br><br>Device:         rrqm&#47;s   wrqm&#47;s     r&#47;s     w&#47;s    rMB&#47;s    wMB&#47;s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util<br>sda               0.00   220.00    0.00  483.00     0.00    11.23    47.64   143.94  214.15    0.00  214.15   2.07 100.00<br>dm-5              0.00     0.00    0.00  700.00     0.00    10.83    31.69   191.09  212.01    0.00  212.01   1.43 100.00<br><br>Device:         rrqm&#47;s   wrqm&#47;s     r&#47;s     w&#47;s    rMB&#47;s    wMB&#47;s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util<br>sda               0.00   264.00    0.00  493.00     0.00    10.71    44.49   142.95  304.66    0.00  304.66   2.03 100.00<br>dm-5              0.00     0.00    0.00  776.00     0.00    12.11    31.97   193.24  244.70    0.00  244.70   1.29 100.00<br><br>Device:         rrqm&#47;s   wrqm&#47;s     r&#47;s     w&#47;s    rMB&#47;s    wMB&#47;s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util<br>sda               0.00   249.00    0.00  287.00     0.00     7.54    53.77   144.36  434.90    0.00  434.90   3.48 100.00<br>dm-5              0.00     0.00    0.00  537.00     0.00     8.37    31.91   232.41  329.02    0.00  329.02   1.86 100.00<br>请问下老师,这个万转sas磁盘在写不到10MB的情况下,为什么磁盘使用率100%,等待时间也特别长? <br></div>
                            <span class="time">2019-01-23 10:14</span>
                            
                            <div class="reply">
                                <div class="reply-hd"><span>作者回复</span></div>
                                <p class="reply-content">检查下磁盘是否有硬件错误，比如使用 smartctl</p>
                                <p class="reply-time">2019-01-27 15:55</p>
                            </div>
                            
                        </div>
                    </li>
                    


                </ul>
            </div>
        </div>
    </div>
</div>
</body>
</html>